Skip to content

[Split 4/4] Run the UTS through the server door's builders - #1235

Draft
umair-ably wants to merge 13 commits into
integration/v2from
integration/split-4-uts-side-modes
Draft

umair-ably wants to merge 13 commits into
integration/v2from
integration/split-4-uts-side-modes

Conversation

@umair-ably

Copy link
Copy Markdown
Contributor

Fourth PR in the PDR-091b split stack (stacked on #1234; diff shows only this PR's changes). Mirrors ably/ably-js#2294.

What this PR does

  • uts.side mode on the UTS's single construction seam (ClientFactories.kt, used by all 10 unit + integration test files): core (default) keeps the core constructors; server routes both client kinds through PubSubServer.httpClientBuilder/realtimeClientBuilder. The builders only stamp the versionless side flag and pass everything else through, so every UTS behaviour must pass identically through the door — that's the conformance claim this PR adds.
  • CI runs both legs: check.yml (UTS unit) and integration-test.yml (check-uts) each gain a -Duts.side=server invocation.
  • Harness self-test (SideModesTest, the analogue of ably-js's side_modes.test.ts): captures the actual Ably-Agent header via the mock HTTP engine and asserts the selected mode's stamp — bare ably-pubsub-server token in server mode (fails on any /version regression, per ably-common#361), no side token in core mode, ably-java/ always present. Exists so a broken seam can't silently turn the server leg into a duplicate core run. @Timeout(30) so a seam regression fails rather than hangs.
  • Completes DebugOptions.copy() with the four fields base ClientOptions.copy() was also missing (fixed in [Split 2/3] Add io.ably.pubsub:device and :server door packages with side-declaring agents #1233): headers, fallbackHosts, transportParams, agents. This matters here because the door stamping relies on the polymorphic copy() to carry the suite's mock hooks (httpEngine, webSocketEngineFactory, clock, listeners) through construction — the full server-mode suite passing green is the proof.

Where this differs from ably-js (deliberately)

  • No device leg: io.ably.pubsub:device is an Android AAR and cannot run on the JVM the UTS uses (ably-js's device package runs fine in Node). The device door's stamping contract is covered by the instrumentation tests in the device module (emulate.yml). Recorded in the uts README.
  • No constructor-shaped wrapper trickery needed: Kotlin's factory-function seam (TestRealtimeClient {}) already is the ably-js sideConstructor in spirit, so the change is a when on the mode.
  • The two auxiliary AblyRest(...) constructions (liveobjects REST provisioning, proxy token signer) stay on core constructors — they're test infrastructure, not the SDK under test.

Verified locally: full :uts:runUtsUnitTests green in both modes; SideModesTest runs and asserts in both.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@umair-ably

Copy link
Copy Markdown
Contributor Author

The failed check-uts leg turned out to be a real finding, not a test bug — arguably the strongest justification for this PR existing:

40167: a connection or request may only declare itself as a server via a signed x-ably-clientType token claim

Sandbox realtime already enforces that on token auth the agent entry alone may not claim the server side — the side must come from a signed x-ably-clientType token claim, which is PDR-091 deferred decision D2 and isn't implemented anywhere yet. Key-auth server clients connect fine (verified against sandbox with the bare flag); exactly the token-auth tests (TokenRequestTest ×2, AuthReauthTest) failed. ably-js's UTS never hit this because it's fully mocked — this repo's sandbox-backed UTS leg is what caught it.

Resolution here: token-auth tests call assumeSideSupportsTokenAuth() and are skipped (not failed) on the server leg with the 40167/D2 rationale in the message; the core leg still runs them. When D2 lands, the test infra can mint the claim and the assumption gets deleted (tracked in the uts README).

Programme-level implication worth escalating (cc D2's owner): as enforced today, any customer using the server package with token auth gets 40167 — if prod behaves like sandbox, the server door is effectively key-auth-only until D2 ships. That sequencing constraint (D2 before/with the pubsub GA) doesn't appear in the rollout plan.

@umair-ably

Copy link
Copy Markdown
Contributor Author

No more skips on the server leg. The token-auth handling is now per token format:

  • JWTs (AuthReauthTest): the test mints its own JWT (AblyJwt, HS256 via JDK crypto) and adds x-ably-clientType=server on the server leg — the declaration realtime requires on token auth. Verified against sandbox; also the first end-to-end proof the claim path works through the new server package.
  • Native tokens (TokenRequestTest): the format can't carry the claim, but the failing part was only a client authenticating itself with a native token while flagged as a server. That's not how the feature is used — servers mint native tokens for devices — so the test now splits its clients across the seam: the minting client (the createTokenRequest surface under test) goes through the server door on every leg, and the consuming client models the device and stays a plain core client. Full RSA9 coverage through the door, no skip, and the structure now mirrors real usage.

assumeSideSupportsTokenAuth() is deleted — nothing needs it. If the platform later adds the claim to native tokens, a "server self-auth via native token" test can be added then; if it never does, the suite already reflects that combination as unsupported.

umair-ably and others added 5 commits September 16, 2026 12:36
Adds the two public artifacts of the PDR-091b split, following the
ably-js reference implementation (ably-js#2293):

- io.ably.pubsub:server (jar, on :core): PubSubServer.httpClientBuilder()
  and PubSubServer.realtimeClientBuilder(), each accepting everything the
  core constructors accept (ClientOptions, API key or token string).
- io.ably.pubsub:device (aar, on :core-android): PubSubDevice.clientBuilder(),
  one door per PDR-091.
- A shared side helper (shared/src/main/java, compiled into both door
  artifacts rather than published) owns the ably-pubsub-device and
  ably-pubsub-server agent identifiers and the stamping rules: caller
  entries preserved, side entry applied last and unoverridable, caller's
  options never mutated, null passing through to the core's own error.
  The -device/-server suffixes are load-bearing for MAU billing
  classification and documented as such.
- Fixes ClientOptions.copy() to carry headers, fallbackHosts,
  transportParams and agents, which it previously dropped; the doors rely
  on copy() for non-mutating stamping. Covered by a new unit test.
- Server tests include a wire-level assertion that the Ably-Agent HTTP
  header carries ably-pubsub-server/<version> alongside the ably-java
  base identifier; device instrumentation tests assert the same contract
  and run in the emulator matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirrors ably-js#2297, matching the registry entries in ably-common#361:
the side flags are registered versionless (like `browser`), because under
lockstep versioning a side-flag version always duplicates the SDK entry
beside it, and across SDKs a bare version cannot say what it versions.
Identity, version and support status keep travelling on the SDK's own
ably-java/<version> entry. Wire shape:

  ably-java/2.0.0 jre/17 ably-pubsub-server

AgentHeaderCreator already emits a null-valued map entry as a bare token,
so no core change is needed; the doors stamp null and drop their
BuildConfig version plumbing. Tests now assert the flag is present as a
bare token and fail if any /version form regresses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The family identifier follows the package split, per the agent
identifier convention proposed on PDR-091b2 and registered in
ably/ably-common#361: because it flips exactly at the split and the
maintenance branch is never touched, the identifier alone partitions
the fleet - ably-java/* is legacy-package traffic, ably-pubsub-java/*
is new-package traffic. It names the family rather than any one
published artifact; the side a client declares travels as the separate
versionless entry stamped by the door packages:

  ably-pubsub-java/2.0.0 jre/17.0.12 ably-pubsub-server

Requires the ably-pubsub-java registry entry (ably/ably-common#361)
before any release from this branch. Mirrors ably/ably-pubsub-js#2297.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comments describe the behavior in place (versionless registry entries,
the load-bearing suffix) rather than citing decision-record or PR
numbers; those references live in the PR descriptions and READMEs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ifier

SdkWrapperAgentHeaderTest asserts the full agent header and still
expected the pre-rename ably-java family identifier.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ttypic
ttypic force-pushed the integration/split-4-uts-side-modes branch from 91d6930 to b99ea4c Compare September 16, 2026 11:36
ttypic and others added 4 commits September 16, 2026 12:42
[Split 2/3] Add io.ably.pubsub:device and :server door packages with side-declaring agents
PDR-091b requires core, core-android, device and server to release
together on one version, with partial release impossible:

- A verifyReleaseArtifacts task asserts the exact published artifact
  set, group and lockstep VERSION_NAME (catching module-local
  overrides), failing before anything is uploaded. The release workflow
  runs it ahead of publishAndReleaseToMavenCentral.
- A release-dry-run job in check.yml runs the pre-flight plus
  publishToMavenLocal on every PR, so coordinate and version
  regressions surface continuously rather than on release day.
- README gains a note describing the in-development 2.0 artifact set
  and that 1.x installation instructions still apply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirrors ably-js#2294: the UTS constructs every client through a single
seam (TestRealtimeClient/TestRestClient in ClientFactories.kt), now
selected by the uts.side system property — `core` (default) keeps the
core constructors; `server` routes both client kinds through
PubSubServer's side-stamping builders. The builders only stamp the
side-declaring agent flag and pass everything else through (DebugOptions
included, via its copy() override), so conformance must be identical on
both legs; CI runs both for the UTS unit and integration tiers.

A harness self-test (SideModesTest, mirroring ably-js's
side_modes.test.ts) asserts each mode's stamp on the wire via the mock
HTTP engine — bare versionless flag in server mode, none in core mode —
so a broken seam cannot silently degrade the server leg into a duplicate
core run.

Unlike ably-js there is no device leg: io.ably.pubsub:device is an
Android artifact and cannot run on the JVM this suite uses; its stamping
contract is covered by the device module's instrumentation tests.

Also completes DebugOptions.copy() with the same four fields the base
ClientOptions.copy() was missing (headers, fallbackHosts,
transportParams, agents) — the door stamping relies on the polymorphic
copy() to carry the suite's mock hooks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
umair-ably and others added 4 commits September 16, 2026 12:42
The server-mode UTS leg surfaced a real platform behavior: realtime
rejects a token-authenticated connection that declares the server side
through the agent entry alone, with 40167 "a connection or request may
only declare itself as a server via a signed x-ably-clientType token
claim". The signed-claim mechanism is PDR-091 deferred decision D2 and
does not exist yet, so nothing the test infrastructure can mint will
authenticate a token-auth server client.

Key-auth server clients are unaffected (verified against sandbox), so
the two token-auth test classes (TokenRequestTest, AuthReauthTest) now
call assumeSideSupportsTokenAuth() and are reported skipped, not failed,
on the server leg; the core leg still runs them. When D2 lands, the test
infra can mint the claim and the assumption gets deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The UTS spec authenticates this test with a JWT; the Java port had
substituted a native TokenRequest for convenience. Restoring the JWT
(AblyJwt: HS256 via JDK crypto, no external library) lets the test carry
the signed x-ably-clientType=server claim on the server leg — the only
server-side declaration realtime accepts on token auth — so it now runs
on every leg instead of being skipped. Verified against sandbox: the
claim-bearing JWT connects and re-authenticates where the bare agent
flag was rejected with 40167.

assumeSideSupportsTokenAuth stays for the native-token tests
(TokenRequestTest): the native token format cannot carry the claim yet.

Also drops DR/ticket numbers from code comments in this PR's files;
the behavior is described in place instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ntifier

The family identifier shares the ably-pubsub- prefix with the side
flags, so the harness self-test now matches the side identifiers exactly
(via the Side constants) rather than by prefix, and asserts the renamed
family entry is always present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TokenRequestTest's two clients now sit on opposite sides of the seam,
matching how native tokens are really used: the minting client — the
createTokenRequest surface under test — goes through the door on every
leg, so the server leg exercises token minting through the server
package; the consuming client models the device the token was minted
for and is always a plain core client, since a client may not
authenticate itself with a native token while declaring the server side
(realtime rejects that with 40167, and the native token format cannot
carry the required signed claim).

Nothing is skipped on any leg any more, so assumeSideSupportsTokenAuth
is deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ttypic
ttypic force-pushed the integration/split-4-uts-side-modes branch from b99ea4c to dfc2b19 Compare September 16, 2026 11:42
Base automatically changed from integration/split-3-release-tooling to integration/v2 September 16, 2026 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants